package ch.prokopovi.ui.main;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.widget.CursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListAdapter;
import android.widget.TextView;
import ch.prokopovi.PrefsUtil;
import ch.prokopovi.R;
import ch.prokopovi.Util;
import ch.prokopovi.api.provider.PlacesProvider;
import ch.prokopovi.api.struct.Titled;
import ch.prokopovi.db.BestRatesTable.ColumnBestRates;
import ch.prokopovi.db.RatesPlacesTable.ColumnRatesPlaces;
import ch.prokopovi.provider.places.PlacesProviderFactory;
import ch.prokopovi.struct.Master.CurrencyCode;
import ch.prokopovi.struct.Master.OperationType;
import ch.prokopovi.struct.Master.Region;
import ch.prokopovi.ui.AbstractWidgetConfigure;
import ch.prokopovi.ui.main.ChoiseDialog.ChoiseCallback;
import ch.prokopovi.ui.main.api.OpenListener;
import ch.prokopovi.ui.main.api.UpdateListener;
import ch.prokopovi.ui.main.api.Updater;

import com.google.android.apps.analytics.GoogleAnalyticsTracker;
import com.googlecode.androidannotations.annotations.AfterViews;
import com.googlecode.androidannotations.annotations.Click;
import com.googlecode.androidannotations.annotations.EFragment;
import com.googlecode.androidannotations.annotations.OptionsItem;
import com.googlecode.androidannotations.annotations.OptionsMenu;
import com.googlecode.androidannotations.annotations.ViewById;

@EFragment
@OptionsMenu(R.menu.best_menu)
public class BestRatesFragment extends ListFragment implements UpdateListener,
		OnScrollListener {

	private static final String LOG_TAG = "BestRatesFragment";
	private static final DecimalFormat DISTANCE_FORMAT = new DecimalFormat(
			"#.## км");

	private static final int PAGE_SIZE = 40;

	private static final Region[] REGIONS = new Region[] {

			Region.MINSK, //
			Region.KIEV, //
			Region.CHISINAU, //

			Region.BREST, Region.GOMEL, Region.GRODNO, Region.MOGILEV,
			Region.VITEBSK,

			Region.BARANOVICHI, //
			Region.BOBRUISK, //
			Region.BORISOV, //
			Region.LIDA, //
			Region.MOZIR, //
			Region.NOVOPOLOCK,//
			Region.ORSHA, //
			Region.PINSK, //
			Region.POLOCK, //
			Region.SOLIGORSK, //

			Region.MOLODZE4NO, //
			Region.SVETLOGORSK, //
			Region.ZLOBIN, //
			Region.RE4ICA, //
			Region.SLUCK, //
			Region.ZODINO, //

			Region.ODESSA, //
			Region.DONECK, //
			Region.LVOV, //
			Region.DNEPROPETROVSK, //
	};

	private class BestListAdapter extends SimpleCursorAdapter {

		private BestListAdapter(Context context, int layout, Cursor c,
				String[] from, int[] to, int flags) {
			super(context, layout, c, from, to, flags);
		}

		@Override
		public View getView(int i, View convertView, ViewGroup vg) {
			Log.d(LOG_TAG, "getView " + i);

			View rowView = super.getView(i, convertView, vg);

			// distance text
			Integer distance = BestRatesFragment.this.distanceMap[i];
			if (distance != null) {
				String txt = String.valueOf(distance) + " м";
				if (distance > 1000) {

					txt = DISTANCE_FORMAT.format(distance / 1000.0);
				}

				TextView tvDistance = (TextView) rowView
						.findViewById(R.id.tv_item_best_rate_distance);
				tvDistance.setText(txt);
			}

			// open button
			Cursor cursor = getCursor();
			int placeIndex = cursor
					.getColumnIndex(ColumnBestRates.RATES_PLACE_ID.name());
			final int placeId = cursor.getInt(placeIndex);

			ImageButton ibShowMap = (ImageButton) rowView
					.findViewById(R.id.ib_show_map);
			ibShowMap
					.setOnClickListener(new android.view.View.OnClickListener() {
						@Override
						public void onClick(View v) {

							BestRatesFragment.this.tracker
									.trackPageView("/openMap");

							Log.d(LOG_TAG, "open click " + placeId);

							BestRatesFragment.this.openListener.onOpen(placeId);
						}
					});

			return rowView;
		}
	}

	/**
	 * container for place distance info
	 * 
	 * @author Pavel_Letsiaha
	 * 
	 */
	private class SortEntry {
		int id;
		Double rate;
		int type;
		Integer distance; // meters, nulls possible
	}

	private CurrencyCode selectedCurrencyCode;
	private OperationType selectedExchangeType;

	private GoogleAnalyticsTracker tracker;

	private Updater updater;
	private OpenListener openListener;

	private int[] positionMap;
	private Integer[] distanceMap;

	private int itemsAllowedInList = 0;

	@ViewById(R.id.b_exchange_type)
	Button bExchangeType;

	@ViewById(R.id.b_currency)
	Button bCurrency;

	@ViewById(R.id.b_region)
	Button bRegion;

	@Override
	public synchronized void onUpdate() {

		// check/update selected currency
		Region region = this.updater.getRegion();
		PlacesProvider provider = PlacesProviderFactory.find(region);
		if (!provider.isSupported(BestRatesFragment.this.selectedCurrencyCode)) {
			CurrencyCode[] supportedCurrencies = getSupportedCurrencies(region);
			BestRatesFragment.this.selectedCurrencyCode = supportedCurrencies[0];
		}

		// filter ui values
		View v = getView();
		if (v != null) {

			updateUiFilterValues();
		}

		// list
		Cursor cursor = this.updater.getData(this.selectedExchangeType,
				this.selectedCurrencyCode, this.itemsAllowedInList);

		// distance
		updateDistanceMaps(cursor);

		Cursor cursorWrapper = new ReorderingCursorWrapper(cursor,
				this.positionMap);

		CursorAdapter adapter = (CursorAdapter) getListAdapter();
		adapter.changeCursor(cursorWrapper);
	}

	/**
	 * update distance and ordering-by-distance maps according location and
	 * cursor
	 * 
	 * @param cursor
	 */
	void updateDistanceMaps(Cursor cursor) {

		int size = cursor.getCount();
		List<SortEntry> list = new ArrayList<SortEntry>(size);

		Location myLastLocation = this.updater.getLocation();

		// collect
		while (cursor.moveToNext()) {
			SortEntry entry = new SortEntry();
			entry.id = cursor.getInt(cursor
					.getColumnIndex(ColumnBestRates.BEST_RATES_ID.name()));
			entry.rate = cursor.getDouble(cursor
					.getColumnIndex(ColumnBestRates.VALUE.name()));
			entry.type = cursor.getInt(cursor
					.getColumnIndex(ColumnBestRates.EXCHANGE_TYPE_ID.name()));

			entry.distance = null;
			if (myLastLocation != null) {

				double x = cursor.getDouble(cursor
						.getColumnIndex(ColumnRatesPlaces.X.name()));
				double y = cursor.getDouble(cursor
						.getColumnIndex(ColumnRatesPlaces.Y.name()));

				float[] result = new float[1];
				Location.distanceBetween(myLastLocation.getLatitude(),
						myLastLocation.getLongitude(), x, y, result);

				entry.distance = (int) result[0];
			}

			list.add(entry);
		}

		cursor.moveToPosition(-1); // reset position

		// initial order
		int[] initial = new int[size];
		for (int i = 0; i < size; i++) {
			initial[i] = list.get(i).id;
		}

		// sort
		Collections.sort(list, new Comparator<SortEntry>() {
			@Override
			public int compare(SortEntry lhs, SortEntry rhs) {
				int res = 0;

				// compare rate value
				Double diffRate = lhs.rate - rhs.rate;

				boolean isRateDiffZero = Util.isZero(diffRate);
				boolean isDistanceEmpty = lhs.distance == null
						|| rhs.distance == null;

				if (OperationType.BUY.getId() == lhs.type && !isRateDiffZero) {
					diffRate *= -1;
				} // change direction

				if (isRateDiffZero) {

					if (!isDistanceEmpty) {

						// if rate value is the same - compare distance
						int diffDist = lhs.distance - rhs.distance;

						res = diffDist;
					}

				} else {
					res = diffRate > 0 ? 1 : -1;
				}

				return res;
			}
		});

		// build map
		this.positionMap = new int[size];
		this.distanceMap = new Integer[size];
		for (int i = 0; i < size; i++) {

			for (int j = 0; j < size; j++) {
				SortEntry entry = list.get(j);

				if (entry.id == initial[i]) {
					this.positionMap[j] = i;
					this.distanceMap[j] = entry.distance;
					break;
				}
			}
		}
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {

		Log.d(LOG_TAG, "onCreateView");

		super.onCreateView(inflater, container, savedInstanceState);

		View v = inflater.inflate(R.layout.best_layout, container, false);

		v.setLayoutParams(new LinearLayout.LayoutParams(0,
				LayoutParams.MATCH_PARENT, TabsActivity.DUAL_PANE_RATIO));

		return v;
	}

	@Override
	public void onAttach(Activity activity) {

		Log.d(LOG_TAG, "onAttach");

		super.onAttach(activity);

		// This makes sure that the container activity has implemented
		// the callback interface. If not, it throws an exception
		try {
			this.updater = (Updater) activity;
			this.updater.addUpdateListener(this);

			this.openListener = (OpenListener) activity;

		} catch (ClassCastException e) {
			throw new ClassCastException(activity.toString()
					+ " must implement Updater");
		}
	}

	@AfterViews
	void init() {
		Log.d(LOG_TAG, "init");

		this.tracker = this.updater.getTracker();
		this.tracker.trackPageView("/bestRates");

		initFilters();

		if (getListAdapter() == null) {
			Log.d(LOG_TAG, "new list adapter is created");

			// empty adapter until update is performed
			CursorAdapter bestListAdapter = new BestListAdapter(getActivity(),
					R.layout.best_item_layout, null, //
					new String[] { ColumnBestRates.VALUE.name(),
							ColumnBestRates.TIME_UPDATED.name(),
							ColumnRatesPlaces.DESCRIPTION.name(),
							ColumnRatesPlaces.ADDR.name(),
							ColumnRatesPlaces.WORK_HOURS.name(),
							ColumnRatesPlaces.PHONE.name() }, //
					new int[] { R.id.tv_item_best_rate_value,
							R.id.tv_item_best_rate_time,
							R.id.tv_item_best_description,
							R.id.tv_item_best_addr, R.id.tv_item_best_wh,
							R.id.tv_item_best_phones }, //
					0);

			setListAdapter(bestListAdapter);
		} // first launch

		getListView().setOnScrollListener(this);

	}

	/**
	 * collect currencies supported for provider region
	 * 
	 * @param region
	 * @return
	 */
	private CurrencyCode[] getSupportedCurrencies(Region region) {

		PlacesProvider provider = PlacesProviderFactory.find(region);

		Set<CurrencyCode> supported = new LinkedHashSet<CurrencyCode>();
		for (CurrencyCode curr : CurrencyCode.values()) {
			if (provider.isSupported(curr)) {
				supported.add(curr);
			}
		}

		return supported.toArray(new CurrencyCode[supported.size()]);
	}

	@Click(R.id.b_exchange_type)
	void exchangeTypeClick() {
		this.tracker.trackPageView("/bestExchangeType");

		final BestRatesFragment parent = BestRatesFragment.this;

		parent.selectedExchangeType = OperationType.BUY
				.equals(parent.selectedExchangeType) ? OperationType.SELL
				: OperationType.BUY;

		Log.d(LOG_TAG, "selected exchange type " + parent.selectedExchangeType);

		parent.updater.read(false);
	}

	@Click(R.id.b_currency)
	void currencyClick() {
		this.tracker.trackPageView("/bestCurrency");

		final Context context = getActivity();
		final BestRatesFragment parent = BestRatesFragment.this;

		CurrencyCode[] supportedCurrencies = getSupportedCurrencies(this.updater
				.getRegion());

		new ChoiseDialog<CurrencyCode>()
				.init(context, R.string.lbl_choose_currency,
						supportedCurrencies, //
						new ChoiseCallback() {
							@Override
							public void choise(Titled item) {
								Log.d(LOG_TAG, "filter by: " + item);

								CurrencyCode newChoise = (CurrencyCode) item;

								if (newChoise
										.equals(parent.selectedCurrencyCode)) {
									return;
								}// real change

								parent.selectedCurrencyCode = newChoise;

								Log.d(LOG_TAG, "new currency "
										+ parent.selectedCurrencyCode);

								parent.updater.read(false);

							}
						}).create().show();
	}

	@Click(R.id.b_region)
	void regionClick() {

		this.tracker.trackPageView("/bestRegion");

		final Context context = getActivity();
		final BestRatesFragment parent = BestRatesFragment.this;

		ListAdapter adapter = AbstractWidgetConfigure.buildAdapter(context,
				REGIONS);

		AlertDialog.Builder builder = new AlertDialog.Builder(context);
		builder.setTitle(R.string.lbl_choose_region)
				.setAdapter(adapter, new OnClickListener() {

					@Override
					public void onClick(DialogInterface dialog, int which) {
						Log.d(LOG_TAG, "filter by: " + which);

						Region newChoise = REGIONS[which];
						Region oldChoise = parent.updater.getRegion();

						if (newChoise.equals(oldChoise)) {
							return;
						}// real change

						// reset records in list allowed
						BestRatesFragment.this.itemsAllowedInList = PAGE_SIZE;

						parent.updater.setRegion(newChoise);

						Log.d(LOG_TAG, "new region " + newChoise);

						parent.updater.read(false);
					}
				}).show();
	}

	@OptionsItem
	void menuNear() {
		Log.d(LOG_TAG, "open near rates");

		this.updater.shiftFragments();
	}

	private void initFilters() {

		// restore previously selected values
		restoreSelections();

		updateUiFilterValues();
	}

	private void updateUiFilterValues() {
		this.bExchangeType.setText(this.selectedExchangeType.getTitleRes());
		this.bCurrency.setText(this.selectedCurrencyCode.getTitleRes());
		this.bRegion.setText(this.updater.getRegion().getTitleRes());
	}

	@Override
	public void onDestroyView() {
		super.onDestroyView();

		saveSelections();
	}

	/**
	 * restore DDLs selections from prefs if nothing selected
	 * 
	 */
	private void restoreSelections() {

		if (this.updater.getRegion() == null
				|| this.selectedExchangeType == null
				|| this.selectedCurrencyCode == null) {

			SharedPreferences prefs = getActivity().getSharedPreferences(
					PrefsUtil.PREFS_NAME, Context.MODE_PRIVATE);

			int exTypeId = prefs.getInt(getString(R.string.pref_best_ex_type),
					OperationType.BUY.getId());
			int currId = prefs.getInt(getString(R.string.pref_best_curr),
					CurrencyCode.USD.getId());
			int regionId = prefs.getInt(getString(R.string.pref_best_region),
					Region.MINSK.getId());

			this.selectedExchangeType = OperationType.get(exTypeId);
			this.selectedCurrencyCode = CurrencyCode.get(currId);

			Region region = Region.get(regionId);

			this.updater.setRegion(region);

			Log.d(LOG_TAG, "restoring prev/initial selection: "
					+ this.selectedExchangeType + ", "
					+ this.selectedCurrencyCode + ", " + region);
		}
	}

	/**
	 * save DDLs selection to prefs
	 * 
	 */
	private void saveSelections() {

		Context context = getActivity();

		if (context == null)
			return;

		SharedPreferences.Editor prefs = context.getSharedPreferences(
				PrefsUtil.PREFS_NAME, Context.MODE_PRIVATE).edit();

		prefs.putInt(getString(R.string.pref_best_ex_type),
				this.selectedExchangeType.getId());

		prefs.putInt(getString(R.string.pref_best_curr),
				this.selectedCurrencyCode.getId());

		prefs.putInt(getString(R.string.pref_best_region), this.updater
				.getRegion().getId());

		prefs.commit();
	}

	@Override
	public void onScroll(AbsListView view, int firstVisible, int visibleCount,
			int totalCount) {

		boolean loadMore = /* maybe add a padding */
		firstVisible + visibleCount >= totalCount;

		if (loadMore && this.itemsAllowedInList == totalCount) {
			Log.d(LOG_TAG, "load more...");
			this.itemsAllowedInList += PAGE_SIZE;
			this.updater.read(false);
		}
		// adapter.count += visibleCount; // or any other amount
		// adapter.notifyDataSetChanged();
	}

	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {
	}
}
